package shotgun.my.sweetutil;


import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author wulingming
 */
public class RSAUtil {


    public static final String DEFAULT_CHARSET = "UTF-8";

    public static final String KEY_ALGORITHM = "RSA";

    /**
     * 1024 bits 的 RSA 密钥对，最大加密明文大小
     */
    private static final int MAX_ENCRYPT_BLOCK = 117;

    /**
     * 1024 bits 的 RSA 密钥对，最大解密密文大小
     */
    private static final int MAX_DECRYPT_BLOCK = 128;

    /**
     * 生成密钥对
     */
    public static KeyPair initCreateKey(int keysize) throws NoSuchAlgorithmException {
        KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM);
        // 设置密钥对的 bit 数，越大越安全
        keyPairGen.initialize(keysize);
        return keyPairGen.generateKeyPair();
    }

    /**
     * 获取公钥对象(用于加载公钥进来)
     **/
    public static PublicKey getPublicKey(String publicKeyStr) throws NoSuchAlgorithmException, InvalidKeySpecException, UnsupportedEncodingException {
        byte[] decoded = Base64.getDecoder().decode(publicKeyStr.getBytes(DEFAULT_CHARSET));
        return KeyFactory.getInstance(KEY_ALGORITHM).generatePublic(new X509EncodedKeySpec(decoded));
    }

    /**
     * 获取私钥对象(用于加载私钥进来)
     **/
    public static PrivateKey getPrivateKey(String privateKeyStr) throws UnsupportedEncodingException, NoSuchAlgorithmException, InvalidKeySpecException {
        byte[] decoded = Base64.getDecoder().decode(privateKeyStr.getBytes(DEFAULT_CHARSET));
        return KeyFactory.getInstance(KEY_ALGORITHM).generatePrivate(new PKCS8EncodedKeySpec(decoded));
    }

    /**
     * 公钥加密
     */
    public static String encryptByPublicKey(String text, PublicKey publicKey) throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException, IOException {
        //分段加密

        Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
        byte[] bytes = text.getBytes(DEFAULT_CHARSET);
        int inputLen = bytes.length;
        int offLen = 0;//偏移量
        int i = 0;
        ByteArrayOutputStream bops = new ByteArrayOutputStream();
        while (inputLen - offLen > 0) {
            byte[] cache;
            if (inputLen - offLen > MAX_ENCRYPT_BLOCK) {
                cache = cipher.doFinal(bytes, offLen, MAX_ENCRYPT_BLOCK);
            } else {
                cache = cipher.doFinal(bytes, offLen, inputLen - offLen);
            }
            bops.write(cache);
            i++;
            offLen = MAX_ENCRYPT_BLOCK * i;
        }
        bops.close();
        byte[] encryptedData = bops.toByteArray();
        return Base64.getEncoder().encodeToString(encryptedData);
    }


    /**
     * 私钥解密
     */
    public static String decryptByPublicKey(String secretText, PrivateKey privateKey) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeySpecException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException, IOException {
        //分段解密

        Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);
        cipher.init(Cipher.DECRYPT_MODE, privateKey);

        byte[] bytes = Base64.getDecoder().decode(secretText.getBytes(DEFAULT_CHARSET));
        int inputLen = bytes.length;
        int offLen = 0;
        int i = 0;
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        while (inputLen - offLen > 0) {
            byte[] cache;
            if (inputLen - offLen > MAX_DECRYPT_BLOCK) {
                cache = cipher.doFinal(bytes, offLen, MAX_DECRYPT_BLOCK);
            } else {
                cache = cipher.doFinal(bytes, offLen, inputLen - offLen);
            }
            byteArrayOutputStream.write(cache);
            i++;
            offLen = MAX_DECRYPT_BLOCK * i;

        }
        byteArrayOutputStream.close();
        return byteArrayOutputStream.toString();
    }


    /**
     * 签名
     */
    public static String signByPrivateKey(String data, PrivateKey privateKey) throws Exception {
        Signature signature = Signature.getInstance("SHA256withRSA");
        signature.initSign(privateKey);
        signature.update(data.getBytes(DEFAULT_CHARSET));
        return Base64.getEncoder().encodeToString(signature.sign());
    }

    /**
     * 验签
     */
    public static boolean verifyByPublicKey(String data, PublicKey publicKey, String sign) throws Exception {
        Signature signature = Signature.getInstance("SHA256withRSA");
        signature.initVerify(publicKey);
        signature.update(data.getBytes(DEFAULT_CHARSET));
        return signature.verify(Base64.getDecoder().decode(sign.getBytes(DEFAULT_CHARSET)));
    }

    /**
     * 按照字段排序，构建form表单url参数（sign参数不参与）
     * 比如结果：date=xxx&1=xxx&2=xxx&26=xxx&15=xxx&uuid=xxx
     **/
    public static String buildFormParamExceptSign(Map<String, Object> map) {
        return Arrays.stream(map.keySet().toArray(new String[0])).filter(s -> !"sign".equals(s)).sorted().map(s -> s + "=" + map.get(s)).collect(Collectors.joining("&"));
    }

    public static void main(String[] args) throws Exception {
        // 原始明文
        String content = "春江潮水连海平，海上明月共潮生。滟滟随波千万里，何处春江无月明。春江潮水连海平，海上明月共潮生。滟滟随波千万里，何处春江无月明。春江潮水连海平，海上明月共潮生。滟滟随波千万里，何处春江无月明。春江潮水连海平，海上明月共潮生。滟滟随波千万里，何处春江无月明。春江潮水连海平，海上明月共潮生。滟滟随波千万里，何处春江无月明。春江潮水连海平，海上明月共潮生。滟滟随波千万里，何处春江无月明。春江潮水连海平，海上明月共潮生。滟滟随波千万里，何处春江无月明。春江潮水连海平，海上明月共潮生。滟滟随波千万里，何处春江无月明。春江潮水连海平，海上明月共潮生。滟滟随波千万里，何处春江无月明。春江潮水连海平，海上明月共潮生。滟滟随波千万里，何处春江无月明。春江潮水连海平，海上明月共潮生。滟滟随波千万里，何处春江无月明。春江潮水连海平，海上明月共潮生。滟滟随波千万里，何处春江无月明。春江潮水连海平，海上明月共潮生。滟滟随波千万里，何处春江无月明。春江潮水连海平，海上明月共潮生。滟滟随波千万里，何处春江无月明。春江潮水连海平，海上明月共潮生。滟滟随波千万里，何处春江无月明。春江潮水连海平，海上明月共潮生。滟滟随波千万里，何处春江无月明。春江潮水连海平，海上明月共潮生。滟滟随波千万里，何处春江无月明。春江潮水连海平，海上明月共潮生。滟滟随波千万里，何处春江无月明。春江潮水连海平，海上明月共潮生。滟滟随波千万里，何处春江无月明。";

        // 生成密钥对
        KeyPair keyPair = initCreateKey(1024);

        PublicKey publicKey = keyPair.getPublic();
        String publicKeyStr = Base64.getEncoder().encodeToString(publicKey.getEncoded());
        System.out.println("公钥:" + publicKeyStr);

        PrivateKey privateKey = keyPair.getPrivate();
        String privateKeyStr = Base64.getEncoder().encodeToString(privateKey.getEncoded());
        System.out.println("私钥:" + privateKeyStr);


        //从字符串加载读取秘钥
        String newPublicKeyStr = publicKeyStr;
        String newPrivateKeyStr = privateKeyStr;
        // 加密
        String cipherText = encryptByPublicKey(content, getPublicKey(newPublicKeyStr));
        System.out.println("加密后的密文:" + cipherText + ",,,长度:" + cipherText.length());

        // 解密
        String plainText = decryptByPublicKey(cipherText, getPrivateKey(newPrivateKeyStr));
        System.out.println("解密后明文:" + plainText);


        //签名
        String sign = signByPrivateKey(content, privateKey);
        System.out.println("签名：" + sign);

        //验签
        boolean verify = verifyByPublicKey(content, publicKey, sign);
        System.out.println("验签结果：" + verify);


        Map<String, Object> map = new HashMap<>();
        map.put("uuid", "xxx");
        map.put("date", "xxx");
        map.put("2", "xxx");
        map.put("26", "xxx");
        map.put("15", "xxx");
        map.put("1", "xxx");
        map.put("sign", "xxx");


        String s = buildFormParamExceptSign(map);

        System.out.println(s);


    }
}
